/*
 * AssimpImporter.cpp
 *
 *  Created on: Dec 24, 2013
 *      Author: knuke
 */

#include "AssimpImporter.hpp"

#include <assimp/material.h>
#include <assimp/mesh.h>
#include <assimp/postprocess.h>
#include <assimp/types.h>
#include <assimp/vector3.h>
#include <glm/detail/type_vec.hpp>
#include <glm/detail/type_vec3.hpp>
#include <glm/gtc/type_ptr.hpp>

#include "Converter.hpp"
#include "ConverterException.hpp"
#include "Material.hpp"
#include "MaterialGroup.hpp"
#include "Mesh.hpp"
#include "Model.hpp"
#include "Node.hpp"
#include "Triangle.hpp"
#include "Types.hpp"

using std::string;
using std::vector;
using glm::vec3;
using glm::ivec3;
using glm::make_vec3;

namespace gengine
{
  AssimpImporter::AssimpImporter() :
      importer_(new Assimp::Importer()), scene_(0), model_(0)
  {

  }

  AssimpImporter::~AssimpImporter()
  {
    if (importer_)
    {
      delete importer_;
      importer_ = 0;
    }

  }

  Model* AssimpImporter::importModel(string filename)
  {
    scene_ = importer_->ReadFile(filename, 
        aiProcess_JoinIdenticalVertices | aiProcess_Triangulate);

    if (!scene_)
      throw ConverterException(importer_->GetErrorString());
    else
      Converter::verboseWriteLn("Scene imported successfully.");

    model_ = new Model();

    for (uint mesh_idx = 0; mesh_idx < scene_->mNumMeshes; mesh_idx++)
    {
      Mesh* mesh = processMesh(scene_->mMeshes[mesh_idx]);
      model_->meshes_.push_back(mesh);
    }

    for (uint material_idx = 0; material_idx < scene_->mNumMaterials;
        material_idx++)
    {
      Material* material = processMaterial(scene_->mMaterials[material_idx]);
      model_->materials_.push_back(material);
    }

    Node* node = processNode(scene_->mRootNode);

    model_->root_node_->children_.push_back(node);

    return model_;
  }

  Node* AssimpImporter::processNode(aiNode* assimp_node)
  {
    Node* node = new Node();

    for (uint mesh_idx = 0; mesh_idx < assimp_node->mNumMeshes; mesh_idx++)
    {
      uint global_mesh_idx = assimp_node->mMeshes[mesh_idx];
      node->meshes_.push_back(global_mesh_idx);
    }

    for (uint child_idx = 0; child_idx < assimp_node->mNumChildren; child_idx++)
    {
      aiNode* assimp_child = assimp_node->mChildren[child_idx];
      Node* child = processNode(assimp_child);
      child->parent_ = node;
      node->children_.push_back(child);
    }

    return node;
  }

  Mesh* AssimpImporter::processMesh(aiMesh* assimp_mesh)
  {
    Mesh* mesh = new Mesh();

    MaterialGroup* mat_grp = new MaterialGroup();
    mat_grp->material_ = assimp_mesh->mMaterialIndex;

    // Process vertices.
    for (uint v_idx = 0; v_idx < assimp_mesh->mNumVertices; v_idx++)
    {
      aiVector3D ass_v = assimp_mesh->mVertices[v_idx];
      vec3 glm_v = vec3(ass_v[0], ass_v[1], ass_v[2]); 
      mesh->vertices_.push_back(glm_v);
    }

    for (uint face_idx = 0; face_idx < assimp_mesh->mNumFaces; face_idx++)
    {
      aiFace face = assimp_mesh->mFaces[face_idx];
      vector<int> vertex_indices;
      for (uint index_idx = 0; index_idx < face.mNumIndices; index_idx++)
        vertex_indices.push_back(face.mIndices[index_idx]);

      if (face.mNumIndices == 3)
      {
        ivec3 t = make_vec3(&vertex_indices[0]);
        Triangle* tri = new Triangle(t);
        mat_grp->triangles_.push_back(tri);
      }
    }

    mesh->material_groups_.push_back(mat_grp);

    return mesh;
  }

  Material* AssimpImporter::processMaterial(aiMaterial* assimp_material)
  {
    Material* material = new Material();

    float alpha = 1.f;
    assimp_material->Get(AI_MATKEY_OPACITY, alpha);

    aiColor3D ambient(0.f, 0.f, 0.f);
    assimp_material->Get(AI_MATKEY_COLOR_DIFFUSE, ambient);

    aiColor3D diffuse(0.f, 0.f, 0.f);
    assimp_material->Get(AI_MATKEY_COLOR_DIFFUSE, diffuse);

    material->alpha_ = alpha;
    material->ambient_ = glm::vec3(ambient.r, ambient.g, ambient.b);
    material->diffuse_ = glm::vec3(diffuse.r, diffuse.g, diffuse.b);

    return material;
  }

}
